#include <iostream>

#include "barter_market.hpp"
#include "citizen.hpp"
#include "city.hpp"
#include "foreach.hpp"
#include "formula.hpp"
#include "item_type.hpp"
#include "model.hpp"
#include "money_market.hpp"
#include "raster.hpp"
#include "random.hpp"
#include "string_utils.hpp"
#include "terrain_improvement.hpp"
#include "tile.hpp"
#include "wml_node.hpp"
#include "wml_utils.hpp"

namespace {
game_logic::const_formula_ptr survive_formula;
game_logic::const_formula_ptr reproduce_formula;
game_logic::const_formula_ptr tile_attractiveness_formula;
game_logic::const_formula_ptr wages_wanted_formula;
std::map<std::string, game_logic::const_formula_ptr> produce_score_formulae;
std::map<std::string, int> item_use_map;

typedef std::map<std::pair<std::string,std::string>, game_logic::const_formula_ptr> trade_formula_mapping;
trade_formula_mapping trade_formula_map;

typedef std::map<std::string, game_logic::const_formula_ptr> buy_sell_mapping;
buy_sell_mapping buy_formula_map, sell_formula_map;
buy_sell_mapping buy_quantity_formula_map, sell_quantity_formula_map;
}

void citizen::init(wml::const_node_ptr node)
{
	survive_formula = game_logic::formula::create_optional_formula(node->attr("survive_formula"));
	reproduce_formula = game_logic::formula::create_optional_formula(node->attr("reproduce_formula"));
	tile_attractiveness_formula.reset(new game_logic::formula(node->attr("tile_attractiveness_formula")));
	wages_wanted_formula = game_logic::formula::create_optional_formula(node->attr("wages_wanted_formula"));

	wml::const_node_ptr produce = node->get_child("produce");
	if(produce) {
		for(wml::node::const_attr_iterator i = produce->begin_attr(); i != produce->end_attr(); ++i) {
			produce_score_formulae[i->first].reset(new game_logic::formula(i->second));
		}
	}

	wml::const_node_ptr use = node->get_child("use");
	if(use) {
		for(wml::node::const_attr_iterator i = use->begin_attr(); i != use->end_attr(); ++i) {
			item_use_map[i->first] = atoi(i->second.c_str());
		}
	}

	wml::const_node_ptr trades = node->get_child("trades");
	if(trades) {
		for(wml::node::const_attr_iterator i = trades->begin_attr(); i != trades->end_attr(); ++i) {
			std::vector<std::string> keys = util::split(i->first, '_');
			if(keys.size() == 2) {
				trade_formula_map[std::pair<std::string,std::string>(keys[0], keys[1])].reset(new game_logic::formula(i->second));
			}
		}
	}

	wml::const_node_ptr buys = node->get_child("buys");
	if(buys) {
		for(wml::node::const_attr_iterator i = buys->begin_attr(); i != buys->end_attr(); ++i) {
			buy_formula_map[i->first].reset(new game_logic::formula(i->second));
		}
	}

	wml::const_node_ptr sells = node->get_child("sells");
	if(sells) {
		for(wml::node::const_attr_iterator i = sells->begin_attr(); i != sells->end_attr(); ++i) {
			sell_formula_map[i->first].reset(new game_logic::formula(i->second));
		}
	}

	wml::const_node_ptr buy_quantities = node->get_child("buy_quantities");
	if(buy_quantities) {
		for(wml::node::const_attr_iterator i = buy_quantities->begin_attr(); i != buy_quantities->end_attr(); ++i) {
			buy_quantity_formula_map[i->first].reset(new game_logic::formula(i->second));
		}
	}

	wml::const_node_ptr sell_quantities = node->get_child("sell_quantities");
	if(sell_quantities) {
		for(wml::node::const_attr_iterator i = sell_quantities->begin_attr(); i != sell_quantities->end_attr(); ++i) {
			sell_quantity_formula_map[i->first].reset(new game_logic::formula(i->second));
		}
	}
}

int citizen::rate_tile(const hex::tile& t)
{
	if(!tile_attractiveness_formula) {
		return 0;
	}

	return tile_attractiveness_formula->execute(t).as_int();
}

citizen::citizen() : age_(0), level_(0), occupation_(NULL)
{}

citizen::~citizen()
{}

void citizen::set_allegiance(const government_ptr& gov)
{
	allegiance_ = gov;
}

bool citizen::reproduces() const
{
	if(!reproduce_formula) {
		return false;
	}
	return reproduce_formula->execute(*this).as_bool();
}

bool citizen::survives(std::string* death_reason) const
{
	if(!survive_formula) {
		return true;
	}
	variant res = survive_formula->execute(*this);
	if(res.is_null()) {
		return true;
	}

	if(death_reason) {
		*death_reason = res.as_string();
	}
	return false;
}

namespace {
class resource_callable : public game_logic::formula_callable {
	const citizen& c_;
	const hex::tile& tile_;
	int quality_;
	variant get_value(const std::string& key) const {
		if(key == "quality") {
			return variant(quality_);
		} else if(key == "market") {
			if(tile_.influences().empty()) {
				return variant();
			}

			return variant(&tile_.influences().front()->market());
		} else {
			return c_.query_value(key);
		}
	}
public:
	resource_callable(const citizen& c, const hex::tile& t, int quality) : c_(c), tile_(t), quality_(quality)
	{}
};
}

const resource* citizen::produce(const hex::tile& t) const
{
	int highest = 0;
	const resource* result = NULL;
	foreach(const hex::tile::resource_instance& r, t.resources()) {
		const int rating = production_rating(t, r.resource, r.quality);
		if(rating > 0 && result == NULL || rating > highest) {
			highest = rating;
			result = r.resource.get();
		}
	}

	const std::vector<const_resource_ptr>& global_resources = resource::get_global_resources();
	foreach(const const_resource_ptr& resource, global_resources) {
		const int rating = production_rating(t, resource, 12);
		if(rating > 0 && result == NULL || rating > highest) {
			highest = rating;
			result = resource.get();
		}
	}

	return result;
}

int citizen::production_rating(const hex::tile& t, const const_resource_ptr& resource, int quality) const
{
		//see if the resource requires an improvement which isn't present, in
		//which case it's unusable.
		if(resource->improvement().empty() == false && (!t.improvement() || t.improvement()->id() != resource->improvement())) {
			return -1;
		}

		//see if the resource requires an input which we don't have, in which
		//case we can't use it.
		if(resource->uses().empty() == false && items_owned(resource->uses()) < resource->production_rate()) {
			return -1;
		}

		game_logic::const_formula_ptr f = produce_score_formulae[resource->produces()];
		if(!f) {
			return -1;
		}
		resource_callable callable(*this, t, quality + production_bonus(resource));
		const int result = f->execute(callable).as_int();
		return result;
}

int citizen::items_owned(const std::string& name) const
{
	std::map<std::string, int>::const_iterator i = items_.find(name);
	if(i != items_.end()) {
		assert(i->second >= 0);
		return i->second;
	} else {
		return 0;
	}
}

void citizen::get_item(const std::string& name, int quantity)
{
	items_[name] += quantity;
	//merchants may temporary drop below 0.
	//assert(items_[name] >= 0);
}

void citizen::all_items(std::map<std::string, int>& items) const
{
	for(std::map<std::string, int>::const_iterator i = items_.begin(); i != items_.end(); ++i) {
		items[i->first] += i->second;
	}
}

void citizen::process_item_spoilage()
{
	++age_;
	for(std::map<std::string, int>::iterator i = items_.begin(); i != items_.end(); ++i) {
		const_item_type_ptr item = item_type::get(i->first);
		if(item->spoilable() == false) {
			continue;
		}
		const int use = item_use_map[i->first];
		if(use > i->second) {
			//try to find items that can downgrade to this item.
			for(std::map<std::string, int>::iterator j = items_.begin(); j != items_.end(); ++j) {
				const_item_type_ptr downgradable_item = item_type::get(j->first);
				if(downgradable_item->downgrades_to() == i->first) {
					while(i->second < use && j->second > 0) {
						i->second++;
						j->second--;
					}
				}
			}
		}

		if(item->combines_with().empty() == false) {
			std::map<std::string, int>::iterator j = items_.find(item->combines_with());
			if(j != items_.end()) {
				const int combine = std::min<int>(i->second - use, j->second);
				if(combine > 0) {
					i->second -= combine;
					j->second -= combine;
					items_[item->upgrades_to()] += combine;
				}
			}
		}

		const int nitems = i->second;
		for(int n = 0; n != nitems; ++n) {
			if(item->spoils(n < use)) {
				i->second--;
			}
		}
	}
}

std::string citizen::text_description() const
{
	std::ostringstream s;
	s << "citizen " << age_ << " ";
	if(specialization_) {
		s << specialization_->specialization() << " (level " << level_ << ") ";
	}
	for(std::map<std::string, int>::const_iterator i = items_.begin(); i != items_.end(); ++i) {
		s << i->first << ": " << i->second << " ";
	}

	return s.str();
}

void citizen::place_orders(barter_market& mkt)
{
	for(trade_formula_mapping::iterator i = trade_formula_map.begin(); i != trade_formula_map.end(); ++i) {
		if(!i->second) {
			std::cerr << "bad formula...\n";
			continue;
		}

		const int offer_quantity = i->second->execute(*this).as_int();
		if(offer_quantity > 0 && offer_quantity <= items_[i->first.first]) {
			const std::string& offer = i->first.first; 
			const std::string& want = i->first.second; 
			const_item_type_ptr offer_type = item_type::get(offer);
			const_item_type_ptr want_type = item_type::get(want);
			if(offer_type && want_type) {
				barter_market::offer o;
				o.offered = offer_type;
				o.wanted = want_type;
				o.quantity_offered = offer_quantity;
				o.quantity_wanted = 1;
				o.trader = this;
				mkt.add_offer(o);
			}
		}
	}
}

namespace {
class money_market_callable : public game_logic::formula_callable {
	const citizen& c_;
	const money_market& m_;
	variant get_value(const std::string& key) const {
		if(key == "market") {
			return variant(&m_);
		} else {
			return c_.query_value(key);
		}
	}
public:
	money_market_callable(const citizen& c, const money_market& m) : c_(c), m_(m)
	{}
};
}

void citizen::place_orders(money_market& m)
{
	money_market_callable callable(*this, m);
	for(buy_sell_mapping::const_iterator i = buy_quantity_formula_map.begin();
	    i != buy_quantity_formula_map.end(); ++i) {
		const int quantity = i->second->execute(callable).as_int();
		if(quantity > 0) {
			buy_sell_mapping::const_iterator j = buy_formula_map.find(i->first);
			if(j != buy_formula_map.end()) {
				const int price = j->second->execute(callable).as_int();
				if(price > 0) {
					money_market::offer offer;
					offer.type = item_type::get(i->first);
					if(offer.type) {
						offer.quantity = quantity;
						offer.price = price;
						offer.trader = this;
						m.add_buy(offer);
					}
				}
			}
		}
	}

	for(buy_sell_mapping::const_iterator i = sell_quantity_formula_map.begin();
	    i != sell_quantity_formula_map.end(); ++i) {
		const int quantity = i->second->execute(callable).as_int();
		if(quantity > 0) {
			buy_sell_mapping::const_iterator j = sell_formula_map.find(i->first);
			if(j != sell_formula_map.end()) {
				const int price = j->second->execute(callable).as_int();
				if(price > 0) {
					money_market::offer offer;
					offer.type = item_type::get(i->first);
					if(offer.type) {
						offer.quantity = quantity;
						offer.price = price;
						offer.trader = this;
						m.add_sell(offer);
					}
				}
			}
		}
	}

}

void citizen::level_up(const_resource_ptr specialization)
{
	if(specialization_ != specialization) {
		level_ = 0;
		specialization_ = specialization;
	}

	++level_;
}

int citizen::production_bonus(const_resource_ptr specialization) const
{
	if(specialization == specialization_) {
		return level_;
	} else {
		return 0;
	}
}

namespace {
class job_callable : public game_logic::formula_callable {
	const citizen& c_;
	const const_job_ptr& j_;
	const money_market& m_;
	const hex::tile& t_;
	int rank_;
	variant get_value(const std::string& key) const {
		if(key == "rank") {
			return variant(rank_);
		} else if(key == "job" && j_) {
			return variant(&*j_);
		} else if(key == "market") {
			return variant(&m_);
		} else if(key == "tile") {
			return variant(&t_);
		} else {
			return c_.query_value(key);
		}
	}
public:
	job_callable(const citizen& c, const const_job_ptr& j, const money_market& m, const hex::tile& t, int rank) : c_(c), j_(j), m_(m), t_(t), rank_(rank)
	{}
};
}

int citizen::would_accept_job(const_job_ptr j, const money_market& m, const hex::tile& t, int rank) const
{
	if(!wages_wanted_formula) {
		return -1;
	}

	job_callable callable(*this, j, m, t, rank);
	return wages_wanted_formula->execute(callable).as_int();
}

int citizen::would_change_occupation(const occupation& o, const money_market& m, const hex::tile& t, int rank) const
{
	if(!wages_wanted_formula) {
		return -1;
	}

	job_callable callable(*this, const_job_ptr(), m, t, rank);
	return wages_wanted_formula->execute(callable).as_int();
}

void citizen::cultural_conversion(citizen& other) const
{
	if(!allegiance_) {
		return;
	}

	const int roll = rand()%100;
	if(roll < 10) {
		std::cerr << "CONVERSION!\n";
		other.set_allegiance(allegiance_);
	}
}

void citizen::draw(rng& rng, double x, double y, double z) const
{
	graphics::const_model_ptr m = graphics::model::get("house");
	if(!m) {
		return;
	}
	if(allegiance_) {
		const SDL_Color& color = allegiance_->color();
		glColor4ub(color.r, color.g, color.b, 255);
	}
	glPushMatrix();
	glTranslatef(x, y, z);
	m->draw();
	glPopMatrix();
	if(allegiance_) {
		glColor4f(1.0, 1.0, 1.0, 1.0);
	}

/*
	SDL_Color color;
	if(allegiance_) {
		color = allegiance_->color();
	} else {
		color = graphics::color_gray();
	}
	SDL_Rect r = {x, y, 8, 5};
	graphics::draw_rect(r, graphics::color_black());
	r.x++;
	r.y++;
	r.w -= 2;
	r.h -= 2;
	graphics::draw_rect(r, color);
*/
}

variant citizen::get_value(const std::string& key) const
{
	std::map<std::string, int>::const_iterator i = items_.find(key);
	if(i != items_.end()) {
		return variant(i->second);
	}

	return variant();
}
